# vim:ts=4:sw=4:noet:
""" Project browser controller """

from c.compiler.spy import SpyClass, STATE_COMPILED, STATE_ERROR
from c.compiler.compiler import Compiler
from c.base.attribute import attribute
from c.base.platform import Platform
from c.base.support import Status, CoreException
from c.ui.wxw.events import *
from dialogs import GetInfoRobot, GetInfoMethod
import logging
import os

class ProjectItem:

	type = attribute("_type", "r")
	obj = attribute("_obj", "r")

	def __init__(self, pc, type):
		self._pc = pc
		self._type = type
		self._obj = None

	def select(self, sourceView=True):
		pass

	def deselect(self, forceCompile=False, runAfter=False):
		if runAfter:
			self._pc.mw.controller.doImageLoad()
			
	def canCompile(self):
		return False

################################################################################
class PackageItem(ProjectItem):
	def __init__(self, pc, type, name):
		ProjectItem.__init__(self, pc, type)
		self.name = name
	
	def select(self, sourceView="empty"):
		if self._pc.mw.shuttingDown:
			return
		self._pc.mw.setRightPanel("src")
		self._pc.mw.setEmptyView()
		self._pc.mw.srctext.setText("")
		self._pc.mw.srctext.Enable(False)
		self._pc.mw.methodBar.setMethodName("")

################################################################################
class ClassItem(ProjectItem):
	def __init__(self, pc, type, name, pkg, obj):
		ProjectItem.__init__(self, pc, type)
		self.name = name
		self.pkgitem = pkg
		self._obj = obj
		self.ui = self._pc.addViewItem(self, self.pkgitem)
	
	def select(self, sourceView="empty"):
		self._pc.mw.setRightPanel("class")
		inLib = self._obj.getPragma("category")[:7]=="Library"
		self._pc.mw.panelClass.setObject(self._obj, inLib)
		self._pc.mw.setEmptyView()
		self._pc.mw.srctext.setText("")
		self._pc.mw.srctext.Enable(False)
		self._pc.mw.methodBar.setMethodName("")

################################################################################
class MethodItem(ProjectItem):
	def __init__(self, pc, type, clsitem, name, obj):
		ProjectItem.__init__(self, pc, type)
		self.name = name
		self.clsitem = clsitem
		self._obj = obj
		self.ui = self._pc.addViewItem(self, self.clsitem)
		if obj.state != STATE_COMPILED:
			self._pc.setViewItemIcon(self, "method-error")
		self.sourceView = "source"
		self.deleted = False

	def select(self, sourceView="empty"):
		self._pc.mw.setRightPanel("src")
		if sourceView=="empty":
			self._pc.mw.setSourceView()
			sourceView = "source"
		jname = "%s>>%s" % (self._obj.incls.name.lower(), self._obj.name.lower())
		self._pc.mw.controller.saveState.setOpt("lastmeth",jname)
		self.sourceView = sourceView
		self._pc.mw.methodBar.setMethodName(self._obj.sig)
		self._pc.mw.srctext.Enable(True)
		if self.clsitem.obj.getPragma("category")[:7]=="Library":
			self._pc.mw.srctext.SetReadOnly(not self._pc.isLibEditable())
		else:
			self._pc.mw.srctext.SetReadOnly(False)
		self._pc.mw.Layout()
		self._pc.mw.setSourceView(self.name)
		stext = self._obj.source
		self._pc.mw.srctext.setText(stext)

	def deselect(self, forceCompile=False, runAfter=False):
		if self.sourceView == "source":
			srctext = self._pc.mw.srctext
			shouldCompile = srctext.GetText()!=self._obj.source
			if forceCompile or shouldCompile:
				self._obj.source = srctext.GetText()
				self._pc.mw.controller.doCompile(
						self, self._obj.sig+" [\n"+srctext.GetText()+"\n]", runAfter)

	def canCompile(self):
		if self.clsitem.obj.getPragma("category")[:7]=="Library":
			if not self._pc.isLibEditable():
				return False
		return True

	def compileDone(self, state):
		if self.deleted:
			return
		icon = self._type
		if not state:
			icon = "method-error"
		self._pc.setViewItemIcon(self, icon)

################################################################################
class RobotItem(ProjectItem):
	def __init__(self, pc, robot, name="Unnamed Robot"):
		ProjectItem.__init__(self, pc, "robot")
		self.robot = robot
		self.name = name
		self.ui = self._pc.addViewItem(self, self._pc.devicesPkg)
		self._pc.view.Expand(self._pc.devicesPkg.ui)

	def remove(self):
		self._pc.view.Delete(self.ui)

	def select(self, sourceView="empty"):
		self._pc.mw.srctext.setText("")
		self._pc.mw.methodBar.setMethodName("")
		self._pc.mw.setEmptyView()
		self._pc.mw.setRightPanel("sim")

################################################################################
class ProjectController:

	view = attribute("_view", "r")
	changed = attribute("_changed", "rw")

	def __init__(self, view, mainwin, mem):
		self.mw = mainwin
		self.mem = mem
		self._view = view
		self._selectedItem = None
		self._changed = False

	def __createPackage(self, pname, icon="package", parent=None):
		jname = "Pkg"+pname.lower()
		if jname in self._jumphash:
			return self._jumphash[jname]
		pkgitem = PackageItem(self, "package", pname)
		pui = None
		if parent:
			pui = parent.ui
		pkg = self._view.addItem(pname, parent=pui, pdata=pkgitem, icontype=icon)
		pkgitem.ui = pkg
		self._jumphash[jname] = pkgitem
		return pkgitem

	def __populatePackages(self):
		glbnames = self.mem.globals.keys()
		glbnames.sort()
		for name in glbnames:
			if name[:4]!="Meta":
				obj = self.mem.getGlobal(name)
				if isinstance(obj, SpyClass):
					# create class item
					cat = obj.getPragma("category").split("-")

					if cat[0]=="Library":
						if len(cat)==1:
							pkg = self.libraryPkg
						else:
							catpkg = self.__createPackage(
									cat[1], 
									icon="category", 
									parent=self.libraryPkg)
							pkg = catpkg
					elif name=="mainClass":
						continue
					else:
						pkg = self.projectPkg
					self.addClassItem(pkg, obj, True, False)

	def isLibEditable(self):
		return self._view.libEditable

	def clear(self):
		self._selectedItem = None
		self._view.DeleteChildren(self._view.root)
		self._mhash = {}
		self._jumphash = {}
		self._classes = []
		self._robots = {}

	def populate(self):
		self.clear()
		self.libraryPkg = self.__createPackage("Library")
		self.projectPkg = self.__createPackage("Project")
		self.devicesPkg = self.__createPackage("Robots", "robots")
		self.__populatePackages()
		self._view.SortChildren(self.libraryPkg.ui)
		self._changed = False

	def getJumpkeys(self):
		return self._jumphash.keys()

	def getProjectItem(self, name):
		jn = name.lower()
		if jn in self._jumphash:
			return self._jumphash[jn]
		return None

	def addRobot(self, id, name, connection):
		if id in self._robots:
			return
		self._robots[id] = RobotItem(self, connection, name)
		jn = "/robot/%s" % id
		self._jumphash[jn] = self._robots[id]

	def removeRobot(self, id):
		self._robots[id].remove()
		del self._robots[id]
		jn = "/robot/%s" % id
		del self._jumphash[jn]

	def addViewItem(self, item, parent):
		return self._view.addItem(item.name, parent.ui, item.type, item)

	def setViewItemIcon(self, item, icon):
		self._view.setItemIcon(item.ui, icon)

	def addMethodItem(self, clsitem, meth, type):
		methitem = MethodItem(self, type, clsitem, meth.name, meth)
		self._mhash[methitem.ui] = methitem
		if type=="method":
			jn = "%s>>%s" % (clsitem.obj.name.lower(), meth.name.lower())
		else:
			jn = "meta%s>>%s" % (clsitem.obj.name.lower(), meth.name.lower())
		self._jumphash[jn] = methitem
		self._view.SortChildren(clsitem.ui)
		self._view.SetFocus()
		self._view.SelectItem(methitem.ui, True)
		
	def addClassItem(self, pkg, cls, addMethods=False, sortPkg=True):
		clsitem = ClassItem(self, "class", cls.name, pkg, cls)
		self._mhash[clsitem.ui] = clsitem
		self._jumphash[cls.name.lower()] = clsitem
		self._classes.append(cls.name)
		if sortPkg:
			self._view.SortChildren(pkg.ui)
			self._view.SetFocus()
			self._view.SelectItem(clsitem.ui, True)
		if addMethods:
			if cls.cls:
				for meth in cls.cls.methods.pdict:
					m = cls.cls.methods.get(meth)
					methitem = MethodItem(self,"cmethod", clsitem, meth, m)
					self._mhash[methitem.ui] = methitem
					jn = "%s>>%s" % (cls.cls.name.lower(), meth.lower())
					self._jumphash[jn] = methitem
			# add methods
			for meth in cls.methods.pdict:
				m = cls.methods.get(meth)
				methitem = MethodItem(self,"method", clsitem, meth, m)
				self._mhash[methitem.ui] = methitem
				jn = "%s>>%s" % (cls.name.lower(), meth.lower())
				self._jumphash[jn] = methitem
			self._view.SortChildren(clsitem.ui)
	
	def removeMethodItem(self, methitem):
		del self._mhash[methitem.ui]
		jn = "%s>>%s" % (methitem.obj.incls.name.lower(), methitem.obj.name.lower())
		del self._jumphash[jn]
		self.selectItem(methitem.clsitem.ui)
		self._view.Delete(methitem.ui)
		self._changed = True

	def removeClassItem(self, clsitem):
		del self._mhash[clsitem.ui]
		del self._jumphash[clsitem.obj.name.lower()]
		self._classes.remove(clsitem.obj.name)
		self._view.UnselectAll()
		item = clsitem.ui
		child, foo = self._view.GetFirstChild( item )
		extra = []
		if child.IsOk():
			while child.IsOk():
				extra.append( child )
				child, foo = self._view.GetNextChild( item, foo )
		extra.append(item)
		map( self._view.Delete, extra )
		self._changed = True

	def refreshCurrentItem(self):
		self.selectItem(self._selectedItem)

	def selectItem(self, itemId):
		if self._selectedItem:
			# FIXME: strange windows closing bug
			try:
				self._view.GetPyData(self._selectedItem).deselect()
			except Exception, e:
				logging.warn("BUG failed to deselect '%s'" % (
						self._view.GetItemText(self._selectedItem)))
		try:
			item = self._view.GetPyData(itemId)
			if item:
				item.select(self.mw.viewMode)
				self._selectedItem = itemId
				self.mw.onProjectSelectItem(item.obj)
		except Exception, e:
			logging.warn("BUG failed to select '%s'" % (
					self._view.GetItemText(self._selectedItem)))

	def getSelectedItem(self):
		if self._selectedItem:
			return self._view.GetPyData(self._selectedItem)
		else:
			return None

	def importClass(self, srcfile):
		try:
			c = Compiler(self.mw.controller.mem, mcheck=False)
			clsname = c.compileFile(srcfile)
			self.addClassItem(
					self.projectPkg,
					self.mw.controller.mem.getGlobal(clsname),
					True)
		except:
			self.mw.onImportFailed(srcfile)

	def getEditableClasses(self):
		if not self.isLibEditable():
			classes = []
			for cn in self._classes:
				cls = self._jumphash[cn.lower()].obj
				if not cls.inLibrary():
					classes.append(cn)
			return classes
		else:
			return self._classes
	
	def getAllClasses(self):
		return self._classes

	def upgradeProject(self):
		bp = Platform.getBasePath()
		classes = self.exportPackage(self.projectPkg, bp)
		self.mw.controller.newProject("")
		for c in classes:
			self.mw.controller.mem.addGlobal(c, None)
		for c in classes:
			fn = os.path.join(bp, "%s.st" % c)
			self.importClass(fn)
			os.remove(fn)
		for c in classes:
			cls = self.mw.controller.mem.getGlobal(c)
			for mn in cls.methods.pdict:
				meth = cls.methods.pdict[mn]
				source = "%s [ %s ]" % (meth.sig, meth.source)
				try:
					self.mw.controller.compiler.compileMethod(cls, source)
				except CoreException, e:
					self.mw.postEvent(UiStateEvent("button-msg", (1,1,0,1, "Check failed: %s" % e)))
					self.mw.logMessage("%s\n" % e)
					selItem = self.mw.project.controller.getSelectedItem()
					meth.state = STATE_ERROR

		self.populate()
		mainCls = self.mw.controller.mem.getMainClass()
		if mainCls:
			self.jumpTo("%s>>main" % mainCls.name.lower())
		self.mw.updateTitle()
		self.mw.controller.firstSave = False
		self._changed = True

	def exportPackage(self, pitem, dir):
		package = pitem.ui
		if pitem and pitem.type=="package":
			child,x = self._view.GetFirstChild(package)
			classes = []
			if child.IsOk():
				while child.IsOk():
					classes.append(self._view.GetPyData(child))
					child,x = self._view.GetNextChild( package,x )
			for cls in classes:
				cls.obj.fileOut(dir)
			return [c.obj.name for c in classes]
		return []
	
	def createNewClass(self, clsname, supername):
		cls = SpyClass(self.mem, clsname, self.mem.getGlobal(supername))
		cls.newClassInit()
		self.mem.addGlobal(clsname, cls)
		self._changed = True
		return cls
	
	def newMethod(self, cls, sig):
		return Compiler(self.mem).compileMethod(cls, sig+" [\n\t\n]")

	def createNewMethod(self, methsig, clsname, isclsmeth=False):
		cls = self.mem.getGlobal(clsname)
		methtype = "method"
		if isclsmeth:
			cls = cls.cls
			methtype = "cmethod"
		methcount = len(cls.methods)
		if len(cls.methods)>50:
			return False, "toomany", ""
		elif cls.hasMethod(methsig):
			return False, "exists", ""
		else:
			meth = self.newMethod(cls, methsig)
			self.mem.addMessage(meth.name)
			self._changed = True
			return True, meth, methtype

	def compileDone(self, evt):
		if not evt or not evt.data:
			print "bad compile"
			return
		obj, state = evt.data
		obj.compileDone(state)

	def jumpTo(self, value):
		if value.lower() in self._jumphash:
			self._view.SetFocus()
			self._view.SelectItem(self._jumphash[value.lower()].ui, True)
			return True
		return False

	def showSource(self, cls, meth, line):
		jn = "%s>>%s" % (cls.lower(), meth.lower())
		return self.jumpTo(jn)	

	def getInfo(self, pitem):
		if pitem:
			if pitem.type=="method":
				res = GetInfoMethod(self.mw, pitem).do()
			elif pitem.type=="cmethod":
				res = GetInfoMethod(self.mw, pitem).do()
			elif pitem.type=="robot":
				res = GetInfoRobot(self.mw, pitem).do()

